#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# ======================================================================
#    Copyright (C) 2006 Robert Muth <robert at muth dot org>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; version 2 of June 1991.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program in form of the file COPYING;
#    if not, write to the
#
#    Free Software Foundation, Inc. <http://www.fsf.org>
#    59 Temple Place, Suite 330,
#    Boston, MA 02111-1307  USA
# ======================================================================

"""
low level sound interface for pytrommler

http://muth.org/Robert/pyTrommler
"""
#======================================================================
# python imports

import pygame
import numpy
import logging
import time
import struct
import sys

__all__ = ["Channel",
           "Sample",
           "WaveProlog",]

EVENT_MAGIC = 666
SAMPLE_RATE = 44100
#======================================================================
class Channel:
    def __init__(self):
        logging.info("sound system init")
        pygame.mixer.init(SAMPLE_RATE, -16)
        pygame.init()
        conf = pygame.mixer.get_init()
        logging.info(str(conf))
        self._channel = pygame.mixer.Channel(0)
        self._channel.set_endevent(EVENT_MAGIC)

        logging.info("sound system init complete")
        return

    def PlaySample(self, sample):
        """
        """
        # One event can be playing and up to one can be queue
        # up, we need to wait until queue is empty
        #print self._channel.get_queue()
        while self._channel.get_queue():
            pygame.event.wait()
        so =  pygame.sndarray.make_sound(sample.array16bit())
        self._channel.queue(so)

        return

    def WaitForLastSampleToFinish(self):
        while self._channel.get_sound():
            time.sleep(1)
            logging.info("wait for samples to finish")
        return

#======================================================================
class Sample:

    def __init__(self):
        # numpy array
        self._array = None
        return

    def IsMono(self):
        sa = self._array
        pair = numpy.sum(sa, 0)
        if pair[0] != pair[1]: return 0
        pair = numpy.average(sa, 0)
        if pair[0] != pair[1]: return 0
        pair = numpy.argmax(sa, 0)
        if pair[0] != pair[1]: return 0
        return 1

    def SetArray(self, array):
        self._array = array
        return

    def SetFromFile(self, filename):
        logging.info("loading %s", filename)
        so = pygame.mixer.Sound(filename)
        array = pygame.sndarray.array(so)
        # pick a larger resultion than needed
        self.SetArray(array.astype(numpy.dtype('i4')))
        logging.info(self.Info())
        return

    def SetZeros(self, length):
        self.SetArray(numpy.zeros((length, 2), numpy.dtype("i4")))
        return

    def Add(self, sample):
        #print "#####",self.Length(), sample.Length(), self._sound_array.shape
        assert self.Length() == sample.Length()
        self._array = self._array + sample._array


    def array16bit(self):
        return self._array.astype(numpy.dtype('i2'))

    def Length(self):
        return self._array.shape[0]

    def DataInfo(self):
        array = self._array
        print "sum ", numpy.sum(array, 0)
        print "argmax ", numpy.argmax(array, 0)
        print "argmin ", numpy.argmin(array, 0)
        print "avg ", numpy.average(array, 0)
        return

    def Slice(self, start, length, volume=1.0, balance=0.0):
        sl = self._array[start: start + length]
        assert volume >= 0
        assert -1.0 <= balance <= 1.0
        left = 1.0
        right = 1.0
        if balance < 0: right += balance
        if balance > 0: left -= balance
        factor = numpy.array([[left * volume, 0.0], [0.0, right * volume]], 'd')
        sl = sl.dot(factor)
        sl = sl.astype(numpy.dtype('i4'))

        delta  = start + length - self.Length()
        if delta > 0:
            #print "overflow %d" % delta
            z = numpy.zeros((delta, 2), numpy.dtype('i4'))
            #print z.shape, slice.shape
            sl = numpy.concatenate((sl, z))
            #print sl.shape, sl.dtype
        sample = Sample()
        sample.SetArray(sl)
        return sample

    def Info(self):
        array = self._array
        try:
            itemsize = array.itemsize()
            dtype = array.typecode()
        except:
            itemsize = array.itemsize
            dtype = array.dtype
        return "%d %s %s %s %s" % (self.IsMono(),
                                   type(array),
                                   str(array.shape),
                                   str(itemsize),
                                   str(dtype))

def WaveProlog(length, rate=SAMPLE_RATE, channels=2, format=1, bits=16, align=4):
    """
    format "1" is signed
    """
    def Long(l): return  struct.pack("<l", l)
    def Short(s): return  struct.pack("<h", s)


    data_length = length * channels * bits / 8
    return "".join(["RIFF",
                    Long(12 + 8 + 16 + data_length),
                    "WAVE",
                    "fmt ",
                    Long(16), # format chunk size
                    Short(format),
                    Short(channels),
                    Long(rate),
                    Long(rate * channels * bits /8),
                    Short(align),
                    Short(bits),
                    "data",
                    Long(data_length)])

#======================================================================
if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    channel = Channel()

    s1 = Sample()
    s1.SetFromFile(sys.argv[1])
    print s1.Info()

    s2 = Sample()
    s2.SetFromFile(sys.argv[2])
    print s2.Info()

    #channel.PlaySample(s1)

    channel.PlaySample(s2)
    time.sleep(1)
    channel.PlaySample(s2)
    time.sleep(1)
    channel.PlaySample(s2)
    time.sleep(1)
    delta = 5000
    for start in range(0, s1.Length(), delta):
        print start
        slice = s1.Slice(start, delta, start / float(s1.Length()))
        channel.PlaySample(slice)
    channel.WaitForLastSampleToFinish()

